//
//  DMLScrollView.m
//  DMLParallaxHeader
//
//  Created by 戴明亮 on 2017/8/31.
//  Copyright © 2017年 DML. All rights reserved.
//

#import "DMLScrollView.h"
#import "DMLParallaxHeader.h"

@interface DMLScrollViewDelegateForwarder : NSObject <DMLScrollViewDelegate>
@property (nonatomic,weak) id <DMLScrollViewDelegate>delegate; // 外界的代理对象

@end



@interface DMLScrollView ()<UIGestureRecognizerDelegate>
@property (nonatomic, strong) DMLScrollViewDelegateForwarder *forwarder;
@property (nonatomic, strong) NSMutableArray<UIScrollView *>*observedViews;
@end

@implementation DMLScrollView{
    BOOL _isObserving;
    BOOL _lock;
}


static void * const kDMLScrollViewKVOContext = (void*)&kDMLScrollViewKVOContext;

@synthesize delegate = _delegate;
@synthesize bounces = _bounces;

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    
    if (self) {
        [self initialize];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
        [self initialize];
    }
    return self;
}


- (void)initialize
{
   
    
   self.forwarder = [[DMLScrollViewDelegateForwarder alloc] init];
    // 让forwarder 成为UIscrollView的代理
    super.delegate = self.forwarder;
    

    // 取消垂直滚动条
    self.showsVerticalScrollIndicator = NO;
    // 固定一个方向滚动
    self.directionalLockEnabled = YES;
    // 添加弹簧小姑
    self.bounces = YES;
    
    // 在拖动scrollview时支持touchEvent事件 默认是Yes 拖动的时候touchEvent事件不响应
    self.panGestureRecognizer.cancelsTouchesInView = NO;
    
    // 初始化
    self.observedViews = [NSMutableArray array];
    
    // 添加鉴定
    [self addObserver:self forKeyPath:NSStringFromSelector(@selector(contentOffset)) options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld  context:kDMLScrollViewKVOContext];
    
    // 默认值 YES
    _isObserving = YES;
    
}


#pragma mark Properties 重新设置外界的代理 多重代理

- (void)setDelegate:(id<DMLScrollViewDelegate>)delegate
{
    
    // 让外界设置的代理变成 self.forwarder 属性的代理
    self.forwarder.delegate = delegate;
    
    // 这里防止外界设置了UIScrollView的代理导致出现问题 重新需要强制重新设置
    super.delegate = nil;
    super.delegate = self.forwarder;

    // 这里要设置UIScrollView的代理
    // 所以说要用super
}


- (id<DMLScrollViewDelegate>)delegate
{
    // 返回外界的代理
    return self.forwarder.delegate;
}


#pragma mark <UIGestureRecognizerDelegate>
// 系统默认同一时间内只能响应一个手势 如果需要同一时间响应多个手势需要实现此代理方法
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(nonnull UIGestureRecognizer *)otherGestureRecognizer
{
    
    NSLog(@"gestureRecognizer.view = %@",gestureRecognizer.view);
    NSLog(@"otherGestureRecognizer = %@", otherGestureRecognizer.view);
    // 如果当前otherGestureRecognizer 的View是 自己则不用传递事件
    if (otherGestureRecognizer.view == self){
        return NO;
    }
    
    // 忽略其他手势 如果不是拖动手势 返回 No
    if (![gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
        return NO;
    }
    
    
    // 如果水平滚动返回No 如果X轴拖动的速度大于Y轴的拖动速度说明是水平滚动
   CGPoint velocity = [(UIPanGestureRecognizer *)gestureRecognizer velocityInView:self];
    
    if (fabs(velocity.x) > fabs(velocity.y)) {
        return NO;
    }
    
    //只考虑滚动视图的pan
    if (![otherGestureRecognizer.view isKindOfClass:[UIScrollView class]]) {
        return NO;
    }
    
    
    UIScrollView *scrollView = (id)otherGestureRecognizer.view;
    // scrollView 的父视图是uitableView 则返回No
    if ([scrollView.superview isKindOfClass:[UITableView class]]) {
        return NO;
    }
    
    BOOL shouldScroll = YES;
    if ([self.delegate respondsToSelector:@selector(scrollView:shouldScrollWithSubView:)]) {
        shouldScroll = [self.delegate scrollView:self shouldScrollWithSubView:scrollView];;
    }
    
    
    // 获取到其他的是 scrollview
    if (shouldScroll) {
        [self addObservedView:scrollView];
    }
    
    return shouldScroll;
}
#pragma mark KVO

- (void)addObserverToView:(UIScrollView *)scrollView
{
   
    // 其他scrollview此时如果是 contentOffset.y 向上的话 需要锁住状态 当偏移量为0的时候需要解锁 保证本scrollview先滚动
    _lock = scrollView.contentOffset.y > -scrollView.contentInset.top;
    
    // 让其成为监听者
    [scrollView addObserver:self forKeyPath:NSStringFromSelector(@selector(contentOffset)) options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld  context:kDMLScrollViewKVOContext];
    
    
}


- (void)removeObserverFromView:(UIScrollView *)scrollView
{
    @try {
        [scrollView removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentOffset)) context:kDMLScrollViewKVOContext];
    } @catch (NSException *exception) {
        NSLog(@"移除监听异常奔溃");
    }

}



- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
    
    if (context == kDMLScrollViewKVOContext && [keyPath isEqualToString:NSStringFromSelector(@selector(contentOffset))]) {
        
        // 新值
        CGPoint new = [[change objectForKey:NSKeyValueChangeNewKey] CGPointValue];
        NSLog(@"新值-%@",NSStringFromCGPoint(new));
        // 老值
        CGPoint old  = [[change objectForKey:NSKeyValueChangeOldKey] CGPointValue];
        NSLog(@"老值-%@",NSStringFromCGPoint(old));
        // 老值和新值(在Y轴上的)得差
        CGFloat diff = old.y - new.y; // 如果 diff > 0 向下滚动 ； 如果 diff < 0 向上滚动
        NSLog(@"差值->%f",diff);
        
        // 如果差值为0 并且此时的监听已经结束 那么直接返回不再执行
        if (diff == 0.0 ||  _isObserving == NO) {
            return;
        }
        NSLog(@"---------->%@",object);
        // 以上部分为本ScrollView 和外界其他ScrollView共同监听的代码
        // 如果监听对象是自己
        if (object == self) {
            NSLog(@"scrollView.contentOffset.y = %f",self.contentOffset.y);
            NSLog(@"scrollView.contentInset.top = %f",self.contentInset.top);
            NSLog(@"_lock--->%d",_lock);
            // 如果此时 _lock = yes 锁住状态 那么久将本ScrollView的偏移量一直保持最小值 原来的老值不变
            if (diff > 0 && _lock) { // 向下滚动
                [self scrollView:self setContentOffset:old];
            } else if (self.contentOffset.y < -self.contentInset.top && self.bounces == NO){ // 向下拉伸禁止
                [self scrollView:self setContentOffset:CGPointMake(self.contentOffset.x, -self.contentInset.top)];
            }else if (self.contentOffset.y > -self.parallaxHeader.minimumHeight){ // 向上滚动如果达到最小值 那么最外层的ScrollView就不在进行滚动 也就是悬停的效果
                [self scrollView:self setContentOffset:CGPointMake(self.contentOffset.x, -self.parallaxHeader.minimumHeight)];
            }
   
        }else{// 如果监听的是其他的SCrollview
            
            UIScrollView *scrollView = object;
            
            NSLog(@"--->%f",scrollView.contentOffset.y );
            NSLog(@"--->%f",scrollView.contentInset.top);
            NSLog(@"--->%f",self.contentOffset.y);
            // 如果其他的scollView 的偏移量大于0 则说明其他的SCrollView 已经开始向上移动了
            _lock = (scrollView.contentOffset.y > -scrollView.contentInset.top);
            
            // 如果向上滚动时 其他的scrollView不进行滚动 保持原有的值不变 先让最外层的scrollView滚动 直到达到设定的最小值
            if (self.contentOffset.y < -self.parallaxHeader.minimumHeight && diff < 0 && _lock == YES) {
                [self scrollView:scrollView setContentOffset:old];
            }
            
            // 当向下滚动时 最外层的可以自由滚动 但是需要设置其他的scrollView不能进行偏移
            if (_lock == NO && ((self.contentOffset.y > - self.contentInset.top )|| self.bounces)) {
                [self scrollView:scrollView setContentOffset:CGPointMake(scrollView.contentOffset.x, scrollView.contentInset.top)];
            }
            
            
        }

    }else{
        [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
    }
}




#pragma mark Scorlling Views handle

- (void)addObservedView:(UIScrollView *)scrollView
{
    // 判断是否包含监听者
    if (![self.observedViews containsObject:scrollView]) {
        [self.observedViews addObject:scrollView];
        [self addObserverToView:scrollView];
    }
}


// 移除其他SCrollView所有的监听
- (void)removeObservedViews
{
    for (UIScrollView *scrollView in self.observedViews) {
        [self removeObserverFromView:scrollView];
    }
    [self.observedViews removeAllObjects];
    
}

- (void)scrollView:(UIScrollView *)scrollView setContentOffset:(CGPoint)offset
{
    _isObserving = NO;
    // 重新设置偏移量
   NSLog(@"此时的偏移量=>%@",NSStringFromCGPoint(offset));
    // 重新设值后就不需要在进行监听
    scrollView.contentOffset = offset;
    _isObserving = YES;
}


- (void)dealloc
{
    [self removeObserver:self forKeyPath:NSStringFromSelector(@selector(contentOffset)) context:kDMLScrollViewKVOContext];
    [self removeObservedViews];
}

#pragma mark <UIScrollViewDelegate> 此时执行的代理并不是系统得到代理 而是在代理类里面调用本类实现的方法而已 也就是说这两个代理方法随便起什么名字都可以

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    _lock = NO;
    [self removeObservedViews];
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (!decelerate) {
        _lock = NO;
        [self removeObservedViews];
    }
}


/***
 
 translationInView:
 translationInView 返回父视图系统当中，返回横坐标，纵坐标上拖动了多少像素(由此可以判断是不是水平移动)
 
 CGPoint translantion = [UIPanGestureRecognizer translationInView:UIView];
 ABS(translantion.x)/ABS(translantion.y) > 1
 即是判断是否水平移动(x轴拖动距离大于y轴拖动距离)
 
 velocityInView:
 velocityInView 返回指定坐标系统当中拖动的速度，x,y分别代表x轴y轴的拖动速度(矢量)
 
 CGPoint velocity = [UIPanGestureRecognizer velocityInView:UIView];
 velocity.x > 0
 即是判断是否向右滑动的速度是否大于0
 

 */

@end







@implementation DMLScrollViewDelegateForwarder

// 如果该方法不写 那么就不会执行下面的方法 这里系统应该会对实现的代理方法进行预检测
- (BOOL)respondsToSelector:(SEL)aSelector
{
    
    return [self.delegate respondsToSelector:aSelector] || [super respondsToSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    // 将这里的代理 消息转发给外界的代理对象 让其也能执行UIscrollView的代理
    [anInvocation invokeWithTarget:self.delegate];
}



// 如果此时还想让 DMLScrollViewDelegate 执行UIScrollView的代理那么需要 实现几个代理方法 因为这里的消息不会转发到UIScrollView
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    // 此时的scrollView 是 DMLScrollView
    [(DMLScrollView *)scrollView scrollViewDidEndDecelerating:scrollView];
    if ([self.delegate respondsToSelector:_cmd]) {
        [self.delegate scrollViewDidEndDecelerating:scrollView];
    }
    
}


- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
     // 此时的scrollView 是 DMLScrollView
    [(DMLScrollView *)scrollView scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
    if ([self.delegate respondsToSelector:_cmd]) {
        [self.delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate];
    }
    
}



@end































